Passed
Pull Request — master (#29)
by
unknown
03:17
created

WaveFileParser.getFactBytes_   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
dl 0
loc 13
rs 9.95
c 0
b 0
f 0
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileParser class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileReader } from "./wavefile-reader";
31
import { writeString } from "./parsers/write-string";
32
import { packTo, packStringTo, packString, pack } from "./parsers/binary";
33
34
/**
35
 * A class to read and write wav files.
36
 * @extends WaveFileReader
37
 */
38
export class WaveFileParser extends WaveFileReader {
39
  /**
40
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
41
   * The return value of this method can be written straight to disk.
42
   * @return {!Uint8Array} A wav file.
43
   */
44
  toBuffer() {
45
    this.uInt16.be = this.container === "RIFX";
46
    this.uInt32.be = this.uInt16.be;
47
    /** @type {!Array<!Array<number>>} */
48
    let fileBody = [
49
      this.getJunkBytes_(),
50
      this.getDs64Bytes_(),
51
      this.getBextBytes_(),
52
      this.getMextBytes_(),
53
      this.getCartBytes_(),
54
      this.getiXMLBytes_(),
55
      this.getFmtBytes_(),
56
      this.getFactBytes_(),
57
      packString(this.data.chunkId),
58
      pack(this.data.samples.length, this.uInt32),
59
      this.data.samples,
60
      this.getCueBytes_(),
61
      this.getSmplBytes_(),
62
      this.getLISTBytes_(),
63
      this.get_PMXBytes_()
64
    ];
65
    /** @type {number} */
66
    let fileBodyLength = 0;
67
    for (let i = 0; i < fileBody.length; i++) {
68
      fileBodyLength += fileBody[i].length;
69
    }
70
    /** @type {!Uint8Array} */
71
    let file = new Uint8Array(fileBodyLength + 12);
72
    /** @type {number} */
73
    let index = 0;
74
    index = packStringTo(this.container, file, index);
75
    index = packTo(fileBodyLength + 4, this.uInt32, file, index);
76
    index = packStringTo(this.format, file, index);
77
    for (let i = 0; i < fileBody.length; i++) {
78
      file.set(fileBody[i], index);
79
      index += fileBody[i].length;
80
    }
81
    return file;
82
  }
83
84
  /**
85
   * Return the bytes of the 'bext' chunk.
86
   * @private
87
   */
88
  getBextBytes_() {
89
    /** @type {!Array<number>} */
90
    let bytes = [];
91
    this.enforceBext_();
92
    if (this.bext.chunkId) {
93
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
94
      bytes = bytes.concat(
95
        packString(this.bext.chunkId),
96
        pack(602 + this.bext.codingHistory.length, this.uInt32),
97
        writeString(this.bext.description, 256),
98
        writeString(this.bext.originator, 32),
99
        writeString(this.bext.originatorReference, 32),
100
        writeString(this.bext.originationDate, 10),
101
        writeString(this.bext.originationTime, 8),
102
        pack(this.bext.timeReference[0], this.uInt32),
103
        pack(this.bext.timeReference[1], this.uInt32),
104
        pack(this.bext.version, this.uInt16),
105
        writeString(this.bext.UMID, 64),
106
        pack(this.bext.loudnessValue, this.uInt16),
107
        pack(this.bext.loudnessRange, this.uInt16),
108
        pack(this.bext.maxTruePeakLevel, this.uInt16),
109
        pack(this.bext.maxMomentaryLoudness, this.uInt16),
110
        pack(this.bext.maxShortTermLoudness, this.uInt16),
111
        writeString(this.bext.reserved, 180),
112
        writeString(this.bext.codingHistory, this.bext.codingHistory.length)
113
      );
114
    }
115
    this.enforceByteLen_(bytes);
116
    return bytes;
117
  }
118
119
  /**
120
   * Return the bytes of the 'mext' chunk.
121
   * @private
122
   */
123
  getMextBytes_() {
124
    /** @type {!Array<number>} */
125
    let bytes = [];
126
    if (this.mext.chunkId) {
127
      this.mext.chunkSize = 12;
128
      bytes = bytes.concat(
129
        packString(this.mext.chunkId),
130
        pack(this.mext.chunkSize, this.uInt32),
131
        pack(this.mext.soundInformation, this.uInt16),
132
        pack(this.mext.frameSize, this.uInt16),
133
        pack(this.mext.ancillaryDataLength, this.uInt16),
134
        pack(this.mext.ancillaryDataDef, this.uInt16),
135
        writeString(this.mext.reserved, 4)
136
      );
137
    }
138
    return bytes;
139
  }
140
141
  /**
142
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
143
   * @private
144
   */
145
  enforceBext_() {
146
    for (let prop in this.bext) {
147
      if (this.bext.hasOwnProperty(prop)) {
148
        if (this.bext[prop] && prop != "timeReference") {
149
          this.bext.chunkId = "bext";
150
          break;
151
        }
152
      }
153
    }
154
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
155
      this.bext.chunkId = "bext";
156
    }
157
  }
158
159
  /**
160
   * Return the bytes of the 'cart' chunk.
161
   * @private
162
   */
163
  getCartBytes_() {
164
    /** @type {!Array<number>} */
165
    let bytes = [];
166
    let postTimerBytes = this.getPostTimerBytes_();
167
    if (this.cart.chunkId) {
168
      this.cart.chunkSize = 2048 + this.cart.tagText.length;
169
      bytes = bytes.concat(
170
        packString(this.cart.chunkId),
171
        pack(this.cart.chunkSize, this.uInt32),
172
        writeString(this.cart.version, 4),
173
        writeString(this.cart.title, 64),
174
        writeString(this.cart.artist, 64),
175
        writeString(this.cart.cutId, 64),
176
        writeString(this.cart.clientId, 64),
177
        writeString(this.cart.category, 64),
178
        writeString(this.cart.classification, 64),
179
        writeString(this.cart.outCue, 64),
180
        writeString(this.cart.startDate, 10),
181
        writeString(this.cart.startTime, 8),
182
        writeString(this.cart.endDate, 10),
183
        writeString(this.cart.endTime, 8),
184
        writeString(this.cart.producerAppId, 64),
185
        writeString(this.cart.producerAppVersion, 64),
186
        writeString(this.cart.userDef, 64),
187
        pack(this.cart.levelReference, this.uInt32),
188
        postTimerBytes,
189
        writeString(this.cart.reserved, 276),
190
        writeString(this.cart.url, 1024),
191
        writeString(this.cart.tagText, this.cart.tagText.length)
192
      );
193
    }
194
    this.enforceByteLen_(bytes);
195
    return bytes;
196
  }
197
198
  /**
199
   * Return the bytes of the 'cart' postTimers
200
   * @return {!Array<number>} The 'cart' postTimers as an array of bytes.
201
   * @private
202
   */
203
  getPostTimerBytes_() {
204
    /** @type {!Array<number>} */
205
    let postTimers = [];
206
    for (let i = 0; i < 8; i++) {
207
      let usage = "";
208
      let value = 4294967295;
209
      if (i < this.cart.postTimer.length) {
210
        usage = this.cart.postTimer[i].usage;
211
        value = this.cart.postTimer[i].value;
212
      }
213
      postTimers = postTimers.concat(
214
        writeString(usage, 4),
215
        pack(value, this.uInt32)
216
      );
217
    }
218
    return postTimers;
219
  }
220
221
  /**
222
   * Return the bytes of the 'iXML' chunk.
223
   * @return {!Array<number>} The 'iXML' chunk bytes.
224
   * @private
225
   */
226
  getiXMLBytes_() {
227
    /** @type {!Array<number>} */
228
    let bytes = [];
229
    if (this.iXML.chunkId) {
230
      /** @type {!Array<number>} */
231
      let iXMLPackedValue = packString(this.iXML.value);
232
      this.iXML.chunkSize = iXMLPackedValue.length;
233
      bytes = bytes.concat(
234
        packString(this.iXML.chunkId),
235
        pack(this.iXML.chunkSize, this.uInt32),
236
        iXMLPackedValue
237
      );
238
    }
239
    this.enforceByteLen_(bytes);
240
    return bytes;
241
  }
242
243
  /**
244
   * Return the bytes of the 'ds64' chunk.
245
   * @return {!Array<number>} The 'ds64' chunk bytes.
246
   * @private
247
   */
248
  getDs64Bytes_() {
249
    /** @type {!Array<number>} */
250
    let bytes = [];
251
    if (this.ds64.chunkId) {
252
      bytes = bytes.concat(
253
        packString(this.ds64.chunkId),
254
        pack(this.ds64.chunkSize, this.uInt32),
255
        pack(this.ds64.riffSizeHigh, this.uInt32),
256
        pack(this.ds64.riffSizeLow, this.uInt32),
257
        pack(this.ds64.dataSizeHigh, this.uInt32),
258
        pack(this.ds64.dataSizeLow, this.uInt32),
259
        pack(this.ds64.originationTime, this.uInt32),
260
        pack(this.ds64.sampleCountHigh, this.uInt32),
261
        pack(this.ds64.sampleCountLow, this.uInt32)
262
      );
263
    }
264
    //if (this.ds64.tableLength) {
265
    //  ds64Bytes = ds64Bytes.concat(
266
    //    pack(this.ds64.tableLength, this.uInt32),
267
    //    this.ds64.table);
268
    //}
269
    this.enforceByteLen_(bytes);
270
    return bytes;
271
  }
272
273
  /**
274
   * Return the bytes of the 'cue ' chunk.
275
   * @return {!Array<number>} The 'cue ' chunk bytes.
276
   * @private
277
   */
278
  getCueBytes_() {
279
    /** @type {!Array<number>} */
280
    let bytes = [];
281
    if (this.cue.chunkId) {
282
      /** @type {!Array<number>} */
283
      let cuePointsBytes = this.getCuePointsBytes_();
284
      bytes = bytes.concat(
285
        packString(this.cue.chunkId),
286
        pack(cuePointsBytes.length + 4, this.uInt32), // chunkSize
287
        pack(this.cue.dwCuePoints, this.uInt32),
288
        cuePointsBytes
289
      );
290
    }
291
    this.enforceByteLen_(bytes);
292
    return bytes;
293
  }
294
295
  /**
296
   * Return the bytes of the 'cue ' points.
297
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
298
   * @private
299
   */
300
  getCuePointsBytes_() {
301
    /** @type {!Array<number>} */
302
    let points = [];
303
    for (let i = 0; i < this.cue.dwCuePoints; i++) {
304
      points = points.concat(
305
        pack(this.cue.points[i].dwName, this.uInt32),
306
        pack(this.cue.points[i].dwPosition, this.uInt32),
307
        packString(this.cue.points[i].fccChunk),
308
        pack(this.cue.points[i].dwChunkStart, this.uInt32),
309
        pack(this.cue.points[i].dwBlockStart, this.uInt32),
310
        pack(this.cue.points[i].dwSampleOffset, this.uInt32)
311
      );
312
    }
313
    return points;
314
  }
315
316
  /**
317
   * Return the bytes of the 'smpl' chunk.
318
   * @return {!Array<number>} The 'smpl' chunk bytes.
319
   * @private
320
   */
321
  getSmplBytes_() {
322
    /** @type {!Array<number>} */
323
    let bytes = [];
324
    if (this.smpl.chunkId) {
325
      /** @type {!Array<number>} */
326
      let smplLoopsBytes = this.getSmplLoopsBytes_();
327
      bytes = bytes.concat(
328
        packString(this.smpl.chunkId),
329
        pack(smplLoopsBytes.length + 36, this.uInt32), //chunkSize
330
        pack(this.smpl.dwManufacturer, this.uInt32),
331
        pack(this.smpl.dwProduct, this.uInt32),
332
        pack(this.smpl.dwSamplePeriod, this.uInt32),
333
        pack(this.smpl.dwMIDIUnityNote, this.uInt32),
334
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32),
335
        pack(this.smpl.dwSMPTEFormat, this.uInt32),
336
        pack(this.smpl.dwSMPTEOffset, this.uInt32),
337
        pack(this.smpl.dwNumSampleLoops, this.uInt32),
338
        pack(this.smpl.dwSamplerData, this.uInt32),
339
        smplLoopsBytes
340
      );
341
    }
342
    this.enforceByteLen_(bytes);
343
    return bytes;
344
  }
345
346
  /**
347
   * Return the bytes of the 'smpl' loops.
348
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
349
   * @private
350
   */
351
  getSmplLoopsBytes_() {
352
    /** @type {!Array<number>} */
353
    let loops = [];
354
    for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
355
      loops = loops.concat(
356
        pack(this.smpl.loops[i].dwName, this.uInt32),
357
        pack(this.smpl.loops[i].dwType, this.uInt32),
358
        pack(this.smpl.loops[i].dwStart, this.uInt32),
359
        pack(this.smpl.loops[i].dwEnd, this.uInt32),
360
        pack(this.smpl.loops[i].dwFraction, this.uInt32),
361
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32)
362
      );
363
    }
364
    return loops;
365
  }
366
367
  /**
368
   * Return the bytes of the 'fact' chunk.
369
   * @return {!Array<number>} The 'fact' chunk bytes.
370
   * @private
371
   */
372
  getFactBytes_() {
373
    /** @type {!Array<number>} */
374
    let bytes = [];
375
    if (this.fact.chunkId) {
376
      bytes = bytes.concat(
377
        packString(this.fact.chunkId),
378
        pack(this.fact.chunkSize, this.uInt32),
379
        pack(this.fact.dwSampleLength, this.uInt32)
380
      );
381
    }
382
    this.enforceByteLen_(bytes);
383
    return bytes;
384
  }
385
386
  /**
387
   * Return the bytes of the 'fmt ' chunk.
388
   * @return {!Array<number>} The 'fmt' chunk bytes.
389
   * @throws {Error} if no 'fmt ' chunk is present.
390
   * @private
391
   */
392
  getFmtBytes_() {
393
    /** @type {!Array<number>} */
394
    let fmtBytes = [];
395
    if (this.fmt.chunkId) {
396
      /** @type {!Array<number>} */
397
      let bytes = fmtBytes.concat(
398
        packString(this.fmt.chunkId),
399
        pack(this.fmt.chunkSize, this.uInt32),
400
        pack(this.fmt.audioFormat, this.uInt16),
401
        pack(this.fmt.numChannels, this.uInt16),
402
        pack(this.fmt.sampleRate, this.uInt32),
403
        pack(this.fmt.byteRate, this.uInt32),
404
        pack(this.fmt.blockAlign, this.uInt16),
405
        pack(this.fmt.bitsPerSample, this.uInt16),
406
        this.getFmtExtensionBytes_()
407
      );
408
      this.enforceByteLen_(bytes);
409
      return bytes;
410
    }
411
    throw Error('Could not find the "fmt " chunk');
412
  }
413
414
  /**
415
   * Return the bytes of the fmt extension fields.
416
   * @return {!Array<number>} The fmt extension bytes.
417
   * @private
418
   */
419
  getFmtExtensionBytes_() {
420
    /** @type {!Array<number>} */
421
    let extension = [];
422
    if (this.fmt.chunkSize > 16) {
423
      extension = extension.concat(pack(this.fmt.cbSize, this.uInt16));
424
    }
425
    if (this.fmt.audioFormat == 80 && this.fmt.chunkSize == 40) {
426
      extension = extension.concat(
427
        pack(this.fmt.headLayer, this.uInt16),
428
        pack(this.fmt.headBitRate, this.uInt32),
429
        pack(this.fmt.headMode, this.uInt16),
430
        pack(this.fmt.headModeExt, this.uInt16),
431
        pack(this.fmt.headEmphasis, this.uInt16),
432
        pack(this.fmt.headFlags, this.uInt16),
433
        pack(this.fmt.ptsLow, this.uInt32),
434
        pack(this.fmt.ptsHigh, this.uInt32)
435
      );
436
    } else {
437
      if (this.fmt.chunkSize > 18) {
438
        extension = extension.concat(
439
          pack(this.fmt.validBitsPerSample, this.uInt16)
440
        );
441
      }
442
      if (this.fmt.chunkSize > 20) {
443
        extension = extension.concat(pack(this.fmt.dwChannelMask, this.uInt32));
444
      }
445
      if (this.fmt.chunkSize > 24) {
446
        extension = extension.concat(
447
          pack(this.fmt.subformat[0], this.uInt32),
448
          pack(this.fmt.subformat[1], this.uInt32),
449
          pack(this.fmt.subformat[2], this.uInt32),
450
          pack(this.fmt.subformat[3], this.uInt32)
451
        );
452
      }
453
    }
454
    return extension;
455
  }
456
457
  /**
458
   * Return the bytes of the 'LIST' chunk.
459
   * @return {!Array<number>} The 'LIST' chunk bytes.
460
   * @private
461
   */
462
  getLISTBytes_() {
463
    /** @type {!Array<number>} */
464
    let bytes = [];
465
    for (let i = 0; i < this.LIST.length; i++) {
466
      /** @type {!Array<number>} */
467
      let subChunksBytes = this.getLISTSubChunksBytes_(
468
        this.LIST[i].subChunks,
469
        this.LIST[i].format
470
      );
471
      bytes = bytes.concat(
472
        packString(this.LIST[i].chunkId),
473
        pack(subChunksBytes.length + 4, this.uInt32), //chunkSize
474
        packString(this.LIST[i].format),
475
        subChunksBytes
476
      );
477
    }
478
    this.enforceByteLen_(bytes);
479
    return bytes;
480
  }
481
482
  /**
483
   * Return the bytes of the sub chunks of a 'LIST' chunk.
484
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
485
   * @param {string} format The format of the 'LIST' chunk.
486
   *    Currently supported values are 'adtl' or 'INFO'.
487
   * @return {!Array<number>} The sub chunk bytes.
488
   * @private
489
   */
490
  getLISTSubChunksBytes_(subChunks, format) {
491
    /** @type {!Array<number>} */
492
    let bytes = [];
493
    for (let i = 0, len = subChunks.length; i < len; i++) {
494
      if (format == "INFO") {
495
        bytes = bytes.concat(this.getLISTINFOSubChunksBytes_(subChunks[i]));
496
      } else if (format == "adtl") {
497
        bytes = bytes.concat(this.getLISTadtlSubChunksBytes_(subChunks[i]));
498
      }
499
      this.enforceByteLen_(bytes);
500
    }
501
    return bytes;
502
  }
503
504
  /**
505
   * Return the bytes of the sub chunks of a 'LIST' chunk of type 'INFO'.
506
   * @param {!Object} subChunk The 'LIST' sub chunk.
507
   * @return {!Array<number>}
508
   * @private
509
   */
510
  getLISTINFOSubChunksBytes_(subChunk) {
511
    /** @type {!Array<number>} */
512
    let bytes = [];
513
    /** @type {!Array<number>} */
514
    let LISTsubChunkValue = writeString(subChunk.value, subChunk.value.length);
515
    bytes = bytes.concat(
516
      packString(subChunk.chunkId),
517
      pack(LISTsubChunkValue.length + 1, this.uInt32), //chunkSize
518
      LISTsubChunkValue
519
    );
520
    bytes.push(0);
521
    return bytes;
522
  }
523
524
  /**
525
   * Return the bytes of the sub chunks of a 'LIST' chunk of type 'INFO'.
526
   * @param {!Object} subChunk The 'LIST' sub chunk.
527
   * @return {!Array<number>}
528
   * @private
529
   */
530
  getLISTadtlSubChunksBytes_(subChunk) {
531
    /** @type {!Array<number>} */
532
    let bytes = [];
533
    if (["labl", "note"].indexOf(subChunk.chunkId) > -1) {
534
      /** @type {!Array<number>} */
535
      let LISTsubChunkValue = writeString(
536
        subChunk.value,
537
        subChunk.value.length
538
      );
539
      bytes = bytes.concat(
540
        packString(subChunk.chunkId),
541
        pack(LISTsubChunkValue.length + 4 + 1, this.uInt32), //chunkSize
542
        pack(subChunk.dwName, this.uInt32),
543
        LISTsubChunkValue
544
      );
545
      bytes.push(0);
546
    } else if (subChunk.chunkId == "ltxt") {
547
      bytes = bytes.concat(this.getLtxtChunkBytes_(subChunk));
548
    }
549
    return bytes;
550
  }
551
552
  /**
553
   * Return the bytes of a 'ltxt' chunk.
554
   * @param {!Object} ltxt the 'ltxt' chunk.
555
   * @return {!Array<number>}
556
   * @private
557
   */
558
  getLtxtChunkBytes_(ltxt) {
559
    return [].concat(
560
      packString(ltxt.chunkId),
561
      pack(ltxt.value.length + 20, this.uInt32),
562
      pack(ltxt.dwName, this.uInt32),
563
      pack(ltxt.dwSampleLength, this.uInt32),
564
      pack(ltxt.dwPurposeID, this.uInt32),
565
      pack(ltxt.dwCountry, this.uInt16),
566
      pack(ltxt.dwLanguage, this.uInt16),
567
      pack(ltxt.dwDialect, this.uInt16),
568
      pack(ltxt.dwCodePage, this.uInt16),
569
      // should always be a empty string;
570
      // kept for compatibility
571
      writeString(ltxt.value, ltxt.value.length)
572
    );
573
  }
574
575
  /**
576
   * Return the bytes of the '_PMX' chunk.
577
   * @return {!Array<number>} The '_PMX' chunk bytes.
578
   * @private
579
   */
580
  get_PMXBytes_() {
581
    /** @type {!Array<number>} */
582
    let bytes = [];
583
    if (this._PMX.chunkId) {
584
      /** @type {!Array<number>} */
585
      let _PMXPackedValue = packString(this._PMX.value);
586
      this._PMX.chunkSize = _PMXPackedValue.length;
587
      bytes = bytes.concat(
588
        packString(this._PMX.chunkId),
589
        pack(this._PMX.chunkSize, this.uInt32),
590
        _PMXPackedValue
591
      );
592
    }
593
    this.enforceByteLen_(bytes);
594
    return bytes;
595
  }
596
597
  /**
598
   * Return the bytes of the 'junk' chunk.
599
   * @private
600
   */
601
  getJunkBytes_() {
602
    /** @type {!Array<number>} */
603
    let bytes = [];
604
    if (this.junk.chunkId) {
605
      return bytes.concat(
606
        packString(this.junk.chunkId),
607
        pack(this.junk.chunkData.length, this.uInt32), //chunkSize
608
        this.junk.chunkData
609
      );
610
    }
611
    this.enforceByteLen_(bytes);
612
    return bytes;
613
  }
614
615
  /**
616
   * Push a null byte into a byte array if
617
   * the byte count is odd.
618
   * @param {!Array<number>} bytes The byte array.
619
   * @private
620
   */
621
  enforceByteLen_(bytes) {
622
    if (this.padBytes) {
623
      if (bytes.length % 2) {
624
        bytes.push(0);
625
      }
626
    }
627
  }
628
}
629